package com.zhaokuner.echo;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/***
 * 有BUG 有借鉴的地方是连续读取
 */
public class EchoServer {

	public static void main(String[] args) throws Exception {
		EchoServer es = new EchoServer();
		es.startServer();
	}

	private Selector selector;
	private ExecutorService tp = Executors.newCachedThreadPool();
	private static Map<Socket, Long> time_stat = new HashMap<>(10240);

	private void startServer() throws Exception {
		selector = SelectorProvider.provider().openSelector();
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);
		// InetSocketAddress isa = new InetSocketAddress(InetAddress.getLocalHost(), 8000);
		InetSocketAddress isa = new InetSocketAddress(8000);
		ssc.socket().bind(isa);
		SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
		System.out.println("selector register ");
		for (;;) {
			selector.select();
			Set<SelectionKey> readyKeys = selector.selectedKeys();
			Iterator<SelectionKey> i = readyKeys.iterator();
			while (i.hasNext()) {
				SelectionKey sk = i.next();
				i.remove();
				if (sk.isAcceptable()) {
					doAccept(sk);
				} else if (sk.isValid() && sk.isReadable()) {
					doRead(sk);
				} else if (sk.isValid() && sk.isWritable()) {
					doWrite(sk);
				}
			}
		}
	}

	private void doWrite(SelectionKey sk) {
		System.out.println("doWrite");
		SocketChannel channel = (SocketChannel) sk.channel();
		EchoClientModel echoClient = (EchoClientModel) sk.attachment();
		LinkedList<ByteBuffer> outq = echoClient.getOutputQueue();
		ByteBuffer bb = outq.getLast();
		try {
			int len = channel.write(bb);
			System.out.println("write:" + len);
			if (len == -1) {
				disconnection(sk);
				return;
			}
			if (bb.remaining() == 0) {
				// The buffer was completely written, remove it
				outq.removeLast();
			}

		} catch (Exception e) {
			System.out.println("Failed to write to client");
			e.printStackTrace();
			disconnection(sk);
		}

		if (outq.size() == 0) {
			sk.interestOps(SelectionKey.OP_READ);
		}

	}

	private void doRead(SelectionKey sk) {
		SocketChannel channel = (SocketChannel) sk.channel();
		ByteBuffer bb = ByteBuffer.allocate(10);
		int len;
		try {
			len = channel.read(bb);
			System.out.println("read:" + len);
			if (len < 0) {
				disconnection(sk);
				return;
			}

		} catch (Exception e) {
			System.out.println("Failed to read from client");
			e.printStackTrace();
			disconnection(sk);
			return;
		}
		bb.flip();
		tp.execute(new HanldleMsg(sk, bb));

	}

	private void disconnection(SelectionKey sk) {
		sk.cancel();
		System.out.println("SelectionKey cancel");
	}

	private void doAccept(SelectionKey sk) {
		System.out.println("doAccept");
		ServerSocketChannel server = (ServerSocketChannel) sk.channel();
		SocketChannel clientChannel;
		try {
			clientChannel = server.accept();
			clientChannel.configureBlocking(false);
			// Register this channel for reading
			SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);
			// Allocate an EchoClient instance and attach it to this selection key
			EchoClientModel echoClient = new EchoClientModel(selector);
			clientKey.attach(echoClient);

			InetAddress clientAddress = clientChannel.socket().getInetAddress();
			System.out.println("Accepted connection from " + clientAddress.getHostAddress());
		} catch (Exception e) {
			System.out.println("Failed to accept new client");
			e.printStackTrace();
		}
	}

}
